package org.ecbox.android.sdk;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import org.ecbox.android.sdk.internal.parser.json.ObjectJsonParser;
import org.ecbox.android.sdk.internal.parser.xml.ObjectXmlParser;
import org.ecbox.android.sdk.internal.util.FileItem;
import org.ecbox.android.sdk.internal.util.RequestParametersHolder;
import org.ecbox.android.sdk.internal.util.WebUtils;

/**
 * 基于REST的TOP客户端。
 * 
 * @author carver.gu
 * @since 1.0, Sep 13, 2009
 */
public class DefaultEopClient implements EopClient {

  private static final String APP_KEY = "appKey"; // v
  private static final String FORMAT = "format";
  private static final String METHOD = "method"; // v
  private static final String TIMESTAMP = "timestamp"; // v
  private static final String VERSION = "v";
  private static final String SIGN = "sign"; // v
  private static final String SIGN_METHOD = "signMethod";
  private static final String PARTNER_ID = "partnerId";
  private static final String ACCESS_TOKEN = "accessKey";

  private String serverUrl;
  private String appKey;
  private String appSecret;
  private String v = "2.0";// 版本
  private String format = Constants.FORMAT_JSON;
  private String signMethod = Constants.SIGN_METHOD_MD5;

  private int connectTimeout = 3000;// 3秒
  private int readTimeout = 15000;// 15秒
  private boolean needCheckRequest = true;
  private boolean needEnableParser = true;

  public DefaultEopClient(String serverUrl, String appKey, String appSecret) {
    this.appKey = appKey;
    this.appSecret = appSecret;
    this.serverUrl = serverUrl;
  }

  public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format) {
    this(serverUrl, appKey, appSecret);
    this.format = format;
  }

  public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
      int readTimeout) {
    this(serverUrl, appKey, appSecret, format);
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
  }

  public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
      int readTimeout, String signMethod) {
    this(serverUrl, appKey, appSecret, format, connectTimeout, readTimeout);
    this.signMethod = signMethod;
  }

  @Override
  public <T extends EopResponse> T execute(EopRequest<T> request) throws ApiException {
    return execute(request, null);
  }

  @Override
  public <T extends EopResponse> T execute(EopRequest<T> request, String accessToken) throws ApiException {
    EopParser<T> parser = null;
    if (this.needEnableParser) {
      if (Constants.FORMAT_XML.equals(this.format)) {
        parser = new ObjectXmlParser<T>(request.getResponseClass());
      } else {
        parser = new ObjectJsonParser<T>(request.getResponseClass());
      }
    }
    return _execute(request, parser, accessToken);
  }

  private <T extends EopResponse> T _execute(EopRequest<T> request, EopParser<T> parser, String accessToken)
      throws ApiException {
    if (this.needCheckRequest) {
      try {
        request.check();// if check failed,will throw ApiRuleException.
      } catch (ApiRuleException e) {
        T localResponse = null;
        try {
          localResponse = request.getResponseClass().newInstance();
        } catch (InstantiationException e2) {
          throw new ApiException(e2);
        } catch (IllegalAccessException e3) {
          throw new ApiException(e3);
        }
        localResponse.setErrorCode(e.getErrCode());
        localResponse.setMsg(e.getErrMsg());
        return localResponse;
      }
    }

    Map<String, Object> rt = doPost(request, accessToken);
    if (rt == null)
      return null;

    T tRsp = null;
    if (this.needEnableParser) {
      try {
        tRsp = parser.parse((String) rt.get("rsp"));
        tRsp.setBody((String) rt.get("rsp"));
      } catch (RuntimeException e) {
        EopLogger.logBizError((String) rt.get("rsp"));
        throw e;
      }
    } else {
      try {
        tRsp = request.getResponseClass().newInstance();
        tRsp.setBody((String) rt.get("rsp"));
      } catch (Exception e) {
      }
    }

    tRsp.setParams((EopHashMap) rt.get("textParams"));
    tRsp.setUrl((String) rt.get("url"));
    if (!tRsp.isSuccess()) {
      EopLogger.logErrorScene(rt, tRsp, appSecret);
    }
    return tRsp;
  }

  public <T extends EopResponse> Map<String, Object> doPost(EopRequest<T> request, String accessToken)
      throws ApiException {
    Map<String, Object> result = new HashMap<String, Object>();

    // 请求参数容器
    RequestParametersHolder requestHolder = new RequestParametersHolder();
    EopHashMap appParams = new EopHashMap(request.getTextParams());
    requestHolder.setApplicationParams(appParams);

    // 添加协议级请求参数
    EopHashMap protocalMustParams = new EopHashMap();
    protocalMustParams.put(METHOD, request.getApiMethodName());
    protocalMustParams.put(VERSION, v);
    protocalMustParams.put(APP_KEY, appKey);
    Long timestamp = request.getTimestamp();// 允许用户设置时间戳
    if (timestamp == null) {
      timestamp = System.currentTimeMillis();
    }

    DateFormat df = new SimpleDateFormat(Constants.DATE_TIME_FORMAT, Locale.US);
    df.setTimeZone(TimeZone.getTimeZone(Constants.DATE_TIMEZONE));
    protocalMustParams.put(TIMESTAMP, df.format(new Date(timestamp)));//
    requestHolder.setProtocalMustParams(protocalMustParams);

    // 协议可选参数
    EopHashMap protocalOptParams = new EopHashMap();
    protocalOptParams.put(FORMAT, format);// 格式
    protocalOptParams.put(SIGN_METHOD, signMethod);// 签名方法
    protocalOptParams.put(ACCESS_TOKEN, accessToken);// 访问令牌
    protocalOptParams.put(PARTNER_ID, Constants.SDK_VERSION);// sdk版本
    requestHolder.setProtocalOptParams(protocalOptParams);

    // 添加签名参数
    try {
      if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
        // 注意签名放到了协议必填参数中
        protocalMustParams.put(SIGN, EopUtils.signEopRequestNew(requestHolder, appSecret, false));
      } else if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
        protocalMustParams.put(SIGN, EopUtils.signEopRequestNew(requestHolder, appSecret, true));
      } else {
        protocalMustParams.put(SIGN, EopUtils.signEopRequest(requestHolder, appSecret));
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }

    StringBuffer urlSb = new StringBuffer(serverUrl);
    try {
      String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(), Constants.CHARSET_UTF8);
      String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), Constants.CHARSET_UTF8);

      urlSb.append("?");
      urlSb.append(sysMustQuery);
      if (sysOptQuery != null & sysOptQuery.length() > 0) {
        urlSb.append("&");
        urlSb.append(sysOptQuery);
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }

    String rsp = null;
    try {
      // 是否需要上传文件
      if (request instanceof EopUploadRequest) {
        EopUploadRequest<T> uRequest = (EopUploadRequest<T>) request;
        Map<String, FileItem> fileParams = EopUtils.cleanupMap(uRequest.getFileParams());
        rsp = WebUtils.doPost(urlSb.toString(), appParams, fileParams, connectTimeout, readTimeout);
      } else {
        rsp = WebUtils.doPost(urlSb.toString(), appParams, connectTimeout, readTimeout);
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }
    result.put("rsp", rsp);
    result.put("textParams", appParams);
    result.put("protocalMustParams", protocalMustParams);
    result.put("protocalOptParams", protocalOptParams);
    result.put("url", urlSb.toString());
    return result;
  }

  public void setNeedCheckRequest(boolean needCheckRequest) {
    this.needCheckRequest = needCheckRequest;
  }

  public void setNeedEnableParser(boolean needEnableParser) {
    this.needEnableParser = needEnableParser;
  }

  public void setNeedEnableLogger(boolean needEnableLogger) {
    EopLogger.setNeedEnableLogger(needEnableLogger);
  }

  @Override
  public void setVersion(String v) {
    this.v = v;
  }

}
